﻿using System;
using System.Collections.Generic;
using System.Linq;
using OpenCvSharp;
using Size = OpenCvSharp.Size;

public class 小题
{
    private class Region
    {
        public int StartY { get; set; }
        public int EndY { get; set; }
    }
    // 处理参数
    private const int BinaryThreshold = 180;
    private const double FillRatioThreshold = 0.4; // 填涂判断阈值

    // 外部提供的填涂块参数
    private Size blockSize;
    private double spacing;

    // 处理结果
    public double AverageBlobSize { get; private set; }
    public int QuestionCount { get; private set; }
    public List<int> OptionsPerQuestion { get; private set; } = new List<int>();
    public List<string > Answers { get; private set; } = new List<string>();

    // 构造函数接收已知参数
    public 小题(Size blockSize, double spacing)
    {
        this.blockSize = blockSize;
        this.spacing = spacing;
        this.AverageBlobSize = (blockSize.Width + blockSize.Height) / 2.0;
    }



    // 主处理方法
    public void Process(Mat image)
    {
        // 重置结果
        QuestionCount = 0;
        OptionsPerQuestion.Clear();
        Answers.Clear();

        // 检查输入图像
        if (image == null || image.Empty())
        {
            Console.WriteLine("错误：输入图像为空");
            return;
        }
              

        // 第二步：分行处理（使用已知尺寸优化）
        var lines = SplitLines(image);

        QuestionCount = lines.Count;
        if (QuestionCount == 0)
        {
            Console.WriteLine("未检测到小题行");
            return;
        }




        //Console.WriteLine($"检测到 {QuestionCount} 行小题");

        // 第三步和第四步：处理每一行
        foreach (var line in lines)
        {
        
           ProcessLine(line);
        }
    }

    // 第二步：分行处理（使用已知尺寸优化）
    private List<Mat> SplitLines(Mat image)
    {
        using var gray = new Mat();
        using var binary = new Mat();

        // 预处理
        Cv2.CvtColor(image, gray, ColorConversionCodes.BGR2GRAY);
        Cv2.Threshold(gray, binary, BinaryThreshold, 255, ThresholdTypes.BinaryInv);

        // 水平投影
        var projection = new int[image.Height+1];
        for (int y = 0; y < image.Height; y++)
        {
            using var row = binary.Row(y);
            projection[y] = row.CountNonZero();
        }

        // 计算行高阈值（基于填涂块参数）
        int minLineHeight = (int)(blockSize.Height * 0.6);
        int maxLineHeight = (int)(blockSize.Height * 2.5);
        int minPeakValue = (int)(blockSize.Height * 0.5);  // 基于填涂块宽度更合理

        // 使用间距参数控制合并敏感度
        int maxGap = (int)(spacing * 0.7);  // 关键改进：利用外部间距参数

        // 找到所有峰值区域并智能合并
        var peakRegions = FindPeakRegions(projection, minPeakValue);
       // var mergedRegions = MergeRegionsWithGap(peakRegions, maxGap);  // 改进的合并方法

        // 提取有效行区域
        var lines = new List<Mat>();
        foreach (var region in peakRegions)
        {
            int height = region.EndY - region.StartY + 1;

            // 添加中心点稳定性检查
            if (height >= minLineHeight &&
                height <= maxLineHeight)
              //  IsStableRegion(projection, region))
            {
                var lineRoi = new Rect(0, region.StartY, image.Width, height);
                lines.Add(new Mat(image, lineRoi));
            }
        }

        return lines;
        
    }
    // 改进的区域合并方法（考虑间距阈值）
    private List<Region> MergeRegionsWithGap(List<Region> regions, int maxGap)
    {
        if (regions.Count == 0) return new List<Region>();

        var sorted = regions.OrderBy(r => r.StartY).ToList();
        var merged = new List<Region> { sorted[0] };

        for (int i = 1; i < sorted.Count; i++)
        {
            var last = merged[merged.Count - 1];
            var current = sorted[i];

            // 关键改进：使用间距阈值控制合并
            if (current.StartY - last.EndY <= maxGap)
            {
                last.EndY = Math.Max(last.EndY, current.EndY);
            }
            else
            {
                merged.Add(current);
            }
        }
        return merged;
    }

    //// 稳定性检查（防止误检孤立噪声）
    //private bool IsStableRegion(int[] projection, Region region)
    //{
    //    int peakCount = 0;
    //    int threshold = (int)(minPeakValue * 0.6);

    //    for (int y = region.StartY; y <= region.EndY; y++)
    //    {
    //        if (projection[y] > threshold) peakCount++;
    //    }
    //    return peakCount >= (region.EndY - region.StartY) * 0.7;
    //}

    // 查找峰值区域
    private List<(int StartY, int EndY)> FindPeakRegions(int[] projection, int minPeakValue)
    {
        
        var regions = new List<(int, int)>();
        bool inPeak = false;
        int peakStart = 0;

        for (int y = 0; y < projection.Length; y++)
        {
            if (projection[y] >= minPeakValue && !inPeak)
            {
                peakStart = y;
                inPeak = true;
            }
            else if (projection[y] < minPeakValue && inPeak)
            {
                regions.Add((peakStart, y - 1));
                inPeak = false;
            }
        }
        return regions;
    }

    // 合并相邻区域
    private List<(int StartY, int EndY)> MergeRegions(
        List<(int StartY, int EndY)> regions, int maxGap)
    {
        if (regions.Count == 0) return regions;

        var merged = new List<(int, int)>();
        var sortedRegions = regions.OrderBy(r => r.StartY).ToList();

        int currentStart = sortedRegions[0].StartY;
        int currentEnd = sortedRegions[0].EndY;

        for (int i = 1; i < sortedRegions.Count; i++)
        {
            if (sortedRegions[i].StartY - currentEnd <= maxGap)
            {
                currentEnd = Math.Max(currentEnd, sortedRegions[i].EndY);
            }
            else
            {
                merged.Add((currentStart, currentEnd));
                currentStart = sortedRegions[i].StartY;
                currentEnd = sortedRegions[i].EndY;
            }
        }
        merged.Add((currentStart, currentEnd));
        return merged;
    }

    // 处理单行
    private void ProcessLine(Mat lineImage)
    {
        
        if (lineImage.Empty())
        {
            OptionsPerQuestion.Add(0);
            Answers.Add("M");
            return;
        }       
        // 第三步：使用已知参数计算选项数量
        int optionCount = CalculateOptionCount(lineImage.Width,out double 比例);
        OptionsPerQuestion.Add(optionCount);
        //lineImage.SaveImage($"R:\\image{比例}.PNG");
        // 第四步：使用精确位置识别答案
        string  answer = RecognizeAnswerWithPosition(lineImage, optionCount, 比例);
        Answers.Add(answer);

        Console.WriteLine($"行处理结果: 选项数={optionCount}, 答案={answer}");
    }

    // 计算选项数量（使用已知间距参数）
    private int CalculateOptionCount(int lineWidth,out double 比例)
    {
        double totalUnit = blockSize.Width + spacing;
        比例 = (lineWidth + spacing) / totalUnit;
        int optionCount = (int)Math.Round((lineWidth +spacing) / totalUnit )-1;
        return Math.Max(2, optionCount);
    }

    // 识别答案（使用已知位置参数）
    private string  RecognizeAnswerWithPosition(Mat lineImage, int optionCount,double 比例)
    {
        double 修正 =1.02;//= 比例 - Math.Round(比例); //可能是正数也可能是附属
        using var gray = new Mat();
          var binary = new Mat();

        if (lineImage.Channels() == 3)
        {
            Cv2.CvtColor(lineImage, gray, ColorConversionCodes.BGR2GRAY);
            Cv2.Threshold(gray, binary, BinaryThreshold, 255, ThresholdTypes.BinaryInv);
        }
        else
        {
            binary = lineImage.Clone();
        }
       // binary.SaveImage("R:\\binary.PNG");
        // 计算填涂块精确位置
        double totalUnit = (blockSize.Width + spacing) ;
       // double pianyi =
        double startX = (lineImage.Width - (optionCount * totalUnit* 修正 )-spacing /4 );

        var filledOptions = new List<int>();
        for (int i = 0; i < optionCount; i++)
        {
            double x = (startX + i * totalUnit)*修正;
            var blockRoi = new Rect((int)(x+spacing), (lineImage.Height - blockSize.Height) / 2,
                                 (int )( blockSize.Width* 修正), blockSize.Height);


            if (blockRoi.Height > binary.Height)
                blockRoi.Height = binary.Height;
            if (blockRoi.Y < 0) blockRoi.Y = 0;
            if (blockRoi.Width > binary.Width - blockRoi.X) blockRoi.Width = binary.Width - blockRoi.X;

            using var blockMat = new Mat(binary, blockRoi);
            double fillRatio = (double)blockMat.CountNonZero() / (blockMat.Width * blockMat.Height);
            //lineImage.Rectangle(blockRoi, Scalar.Red, 1);
            //lineImage.SaveImage($"R:\\R.JPG");

           // blockMat.SaveImage($"R:\\__{i}.JPG");
            if (fillRatio > FillRatioThreshold)
            {
                filledOptions.Add(i);
            }
        }
        string ans = "";
        for (int i = 0;i< filledOptions.Count; i++)
        {
            ans += (char)('A' + filledOptions[i]);
        }
        return ans;
        // 返回结果
        //return filledOptions.Count == 1 ?
        //    (char)('A' + filledOptions[0]) :
        //    '?';
    }
}